package utils

import "time"

type DeviceStatus struct {
	deviceId    string
	code        string
	lastSend    time.Time
	failedCount int
}

type ClientStatus struct {
	account string
	devices *ListItem
}

func (c *ClientStatus) GetDevice(id string) *DeviceStatus {
	if c.devices != nil {
		itm, _ := c.devices.Find(func(data interface{}) bool {
			return data.(*DeviceStatus).deviceId == id
		})
		if itm != nil {
			return itm.Data.(*DeviceStatus)
		}
	}
	status := &DeviceStatus{
		deviceId: id,
	}
	itm := NewListItem(status)
	if nil == c.devices {
		c.devices = itm
	} else {
		c.devices.Append(itm)
	}
	return status
}

type VerifyCodeManager struct {
	expire       time.Duration
	len          int32
	clientStatus *ListItem
}

func NewVerifyCodeManager() *VerifyCodeManager {
	inst := &VerifyCodeManager{
		expire: 60 * time.Second,
		len:    4,
	}

	return inst
}

func (s *VerifyCodeManager) SetExpireSec(expireSec time.Duration) {
	s.expire = expireSec * time.Second
}

func (s *VerifyCodeManager) SetLen(len int32) {
	s.len = len
}

func (s *VerifyCodeManager) GetStatus(account string) *ClientStatus {
	if s.clientStatus != nil {
		itm, _ := s.clientStatus.Find(func(data interface{}) bool {
			return data.(*ClientStatus).account == account
		})
		if itm != nil {
			return itm.Data.(*ClientStatus)
		}
	}

	status := &ClientStatus{
		account: account,
	}
	itm := NewListItem(status)
	if nil == s.clientStatus {
		s.clientStatus = itm
	} else {
		s.clientStatus.Append(itm)
	}
	return status
}

func (s *VerifyCodeManager) GetDevice(account, deviceId string) *DeviceStatus {
	return s.GetStatus(account).GetDevice(deviceId)
}

func (c *VerifyCodeManager) Check(account, id, code string) bool {
	status := c.GetDevice(account, id)
	if time.Now().Before(status.lastSend.Add(c.expire)) && status.code != "" && status.code == code && status.deviceId == id {
		status.lastSend = time.Time{}
		status.code = ""
		status.failedCount = 0
		return true
	}
	status.failedCount++
	if status.failedCount > 5 {
		status.lastSend = time.Time{}
		status.code = ""
		status.failedCount = 0
	}
	return false
}

func (c *VerifyCodeManager) CanSend(account, id string) (bool, string) {
	status := c.GetDevice(account, id)
	if time.Now().After(status.lastSend.Add(c.expire)) {
		code := c.GenCode()
		status.lastSend = time.Now()
		status.code = code
		status.deviceId = id
		return true, code
	}
	return false, ""
}

func (c *VerifyCodeManager) ClearStatus(account, id string) {
	status := c.GetDevice(account, id)
	status.lastSend = time.Time{}
}

func (c *VerifyCodeManager) GenCode() string {
	code := ""
	for i := 0; i < int(c.len); i++ {
		code += IntToString(Rand(0, 9))
	}
	return code
}
